/*-------------------------------------------------------------------------
 *
 * portal.h
 *	  POSTGRES portal 定义。
 *
 * portal 是表示运行或可运行查询的执行状态的抽象。
 * portal 支持 SQL 级别 CURSOR 和协议级别 portal。
 *
 * 仅对包含单个 SELECT 类型查询的 portal 允许滚动（非顺序访问）
 * 和执行挂起。我们不想让客户端在更新类型查询中途暂停！因为
 * 查询重写器不允许任意的 ON SELECT 重写规则，因此只有最初的
 * 更新类型查询才能生成多个计划树；因此在实践中限制为单个查询
 * 并不是问题。
 *
 * 对于 SQL 游标，我们支持三种滚动行为：
 *
 * (1) 未指定 NO SCROLL 也未指定 SCROLL：为保持向后兼容，
 *	   我们在这里允许向后提取，除非这样做会带来额外的运行时开销。
 *
 * (2) 指定了 NO SCROLL：不允许任何向后提取。
 *
 * (3) 指定了 SCROLL：允许所有类型的向后提取，即使为此需要
 *	   影响性能。（如果需要，规划器会在查询计划顶部放置一个
 *	   Materialize 节点）。
 *
 * 情况 #1 通过查看查询本身并确定是否可以支持滚动性而转换为
 * #2 或 #3，而不会引入额外的开销。
 *
 * 协议级别的 portal 没有非顺序提取 API，因此对它们来说
 * 这个区别无关紧要。它们总是被初始化为看起来像是 NO SCROLL 游标。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/portal.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef PORTAL_H
#define PORTAL_H

#include "datatype/timestamp.h"
#include "executor/execdesc.h"
#include "tcop/cmdtag.h"
#include "utils/plancache.h"
#include "utils/resowner.h"

/*
 * 我们为 Portals 提供几种执行策略，具体取决于要执行的查询。
 * （注意：在所有情况下，Portal 仅执行一个源 SQL 查询，因此从用户的角度来看,
 * 只生成一个结果。然而，规则重写器可能将单个源查询扩展为零个或多个实际查询。）
 *
 * PORTAL_ONE_SELECT：门户包含一个单独的 SELECT 查询。我们在需求产生结果时逐步运行
 * 执行器。此策略还支持可保持的游标（执行器的结果可以转存到一个
 * tuplestore，以便在事务完成后访问）。
 *
 * PORTAL_ONE_RETURNING：门户包含一个带有 RETURNING 子句的单个 INSERT/UPDATE/DELETE 查询
 * （可能还有规则重写添加的辅助查询）。首次执行时，我们将门户运行到完成
 * 并将主要查询的结果转存到门户 tuplestore; 然后，根据需求将结果返回给客户端。
 * （我们无法支持在查询进行到一半时暂停，因为 AFTER TRIGGER 代码无法处理，并且
 * 也不希望冒险未能执行所有辅助查询。）
 *
 * PORTAL_ONE_MOD_WITH：门户包含一个单独的 SELECT 查询，但它有数据修改的 CTE。
 * 由于可能需要触发器，目前这种情况与 PORTAL_ONE_RETURNING 相同。
 * 将来可能更像 PORTAL_ONE_SELECT。
 *
 * PORTAL_UTIL_SELECT：门户包含返回类似 SELECT 结果的实用语句
 * （例如，EXPLAIN 或 SHOW）。首次执行时，我们执行该语句并将结果转存到
 * 门户 tuplestore; 然后将结果根据需求返回给客户端。
 *
 * PORTAL_MULTI_QUERY：所有其他情况。在这里，我们不支持部分执行：
 * 门户的查询将在首次调用时完全执行。
 */
typedef enum PortalStrategy
{
	PORTAL_ONE_SELECT,
	PORTAL_ONE_RETURNING,
	PORTAL_ONE_MOD_WITH,
	PORTAL_UTIL_SELECT,
	PORTAL_MULTI_QUERY
} PortalStrategy;

/*
 * Portal 始终处于这些状态之一。如果查询未完成，则可以从 ACTIVE 转换回 READY；
 * 否则我们永远不会在状态上回退。
 */
typedef enum PortalStatus
{
	PORTAL_NEW,					/* 新创建 */
	PORTAL_DEFINED,				/* PortalDefineQuery done */
	PORTAL_READY,				/* PortalStart 完成，可以运行它 */
	PORTAL_ACTIVE,				/* portal 正在运行（无法删除它） */
	PORTAL_DONE,				/* portal 已完成（不要重新运行它） */
	PORTAL_FAILED				/* portal 出错（无法重新运行它） */
} PortalStatus;

typedef struct PortalData *Portal;

typedef struct PortalData
{
	/* 会计数据 */
	const char *name;			/* portal 的名称 */
	const char *prepStmtName;	/* 源准备语句（如果没有则为 NULL） */
	MemoryContext portalContext;	/* portal 的子内存 */
	ResourceOwner resowner;		/* 由 portal 拥有的资源 */
	void		(*cleanup) (Portal portal); /* 清理钩子 */

	/*
	 * 用于记住在何种子事务中创建或使用 portal 的状态数据。
	 * 如果 portal 从先前的事务中保留，则两个 subxids 都是 InvalidSubTransactionId。
	 * 否则，createSubid 是创建的 subxact，activeSubid 是我们运行 portal 的最后一个 subxact。
	 */
	SubTransactionId createSubid;	/* 创建的 subxact */
	SubTransactionId activeSubid;	/* 具有活动的最后一个 subxact */
	int			createLevel;	/* 创建的 subxact 的嵌套级别 */

	/* portal 将执行的查询 */
	const char *sourceText;		/* 查询的文本（自 8.4 起，永远不为 NULL） */
	CommandTag	commandTag;		/* 原始查询的命令标签 */
	QueryCompletion qc;			/* 已执行查询的命令完成数据 */
	List	   *stmts;			/* PlannedStmts 的列表 */
	CachedPlan *cplan;			/* 缓存计划，如果语句来自其中一个 */

	ParamListInfo portalParams; /* 传递给查询的参数 */
	QueryEnvironment *queryEnv; /* 查询的环境 */

	/* 特性/选项 */
	PortalStrategy strategy;	/* 详见上文 */
	int			cursorOptions;	/* DECLARE CURSOR 选项位 */
	bool		run_once;		/* 未使用 */

	/* 状态数据 */
	PortalStatus status;		/* 详见上文 */
	bool		portalPinned;	/* 固定的 portal 不能被删除 */
	bool		autoHeld;		/* 已自动从固定转换为保持（请参见 HoldPinnedPortals()） */

	/* 如果不为 NULL，则 Executor 是活动的；最终调用 ExecutorEnd： */
	QueryDesc  *queryDesc;		/* 执行调用所需的信息 */

	/* 如果 portal 返回元组，则这是它们的 tupdesc： */
	TupleDesc	tupDesc;		/* 结果元组的描述符 */
	/* 这些是用于列的格式代码： */
	int16	   *formats;		/* 每列的格式代码 */

	/*
	 * 最外层 ActiveSnapshot 用于执行门户的查询。对于所有但少数几个实用命令，我们要求存在这样的快照。
	 * 这确保查询结果中的 TOAST 引用可以被解冻，并且有助于减少进程暴露的 xmin 的抖动。
	 */
	Snapshot	portalSnapshot; /* 活动快照，如果没有则为 NULL */

	/*
	 * 我们为持有的游标或 PORTAL_ONE_RETURNING、
	 * PORTAL_ONE_MOD_WITH 或 PORTAL_UTIL_SELECT 查询存储元组的地方。
	 * （持有的游标在事务结束后不再具有任何活动执行器状态。）
	 */
	Tuplestorestate *holdStore; /* 存储可持有游标的地方 */
	MemoryContext holdContext;	/* 包含 holdStore 的内存 */

	/*
	 * 在该快照下读取 holdStore 中的元组。如果元组
	 * 包含 TOAST 引用，我们必须保留该快照的引用，
	 * 因为释放快照可能会允许最近死掉的行被清除，
	 * 以及属于它们的任何 toast 数据。在持有游标的情况下，
	 * 我们通过强制解冻数据来避免需要保持这样的快照。
	 */
	Snapshot	holdSnapshot;	/* 已注册的快照，如果没有则为 NULL */

	/*
	 * atStart、atEnd 和 portalPos 指示当前游标位置。
	 * 在第一行之前 portalPos 为零，获取第 N 行查询后为 N。
	 * 在我们运行到最后时，portalPos = 查询中的行数，atEnd 为真。
	 * 请注意，atStart 意味着 portalPos == 0，但反过来不成立：
	 * 我们可能只退回到第一行，而不是起始位置。
	 * 还请注意，各种代码会检查 atStart 和 atEnd，但仅限于门户移动例程应触及 portalPos。
	 */
	bool		atStart;
	bool		atEnd;
	uint64		portalPos;

	/* 演示数据，主要用于 pg_cursors 系统视图 */
	TimestampTz creation_time;	/* 定义此门户的时间 */
	bool		visible;		/* 将此门户包含在 pg_cursors 中吗？ */
}			PortalData;

/*
 * PortalIsValid
 *		当且仅当门户有效时为真。
 */
#define PortalIsValid(p) PointerIsValid(p)


/* utils/mmgr/portalmem.c 中函数的原型 */
extern void EnablePortalManager(void);
extern bool PreCommit_Portals(bool isPrepare);
extern void AtAbort_Portals(void);
extern void AtCleanup_Portals(void);
extern void PortalErrorCleanup(void);
extern void AtSubCommit_Portals(SubTransactionId mySubid,
								SubTransactionId parentSubid,
								int parentLevel,
								ResourceOwner parentXactOwner);
extern void AtSubAbort_Portals(SubTransactionId mySubid,
							   SubTransactionId parentSubid,
							   ResourceOwner myXactOwner,
							   ResourceOwner parentXactOwner);
extern void AtSubCleanup_Portals(SubTransactionId mySubid);
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PinPortal(Portal portal);
extern void UnpinPortal(Portal portal);
extern void MarkPortalActive(Portal portal);
extern void MarkPortalDone(Portal portal);
extern void MarkPortalFailed(Portal portal);
extern void PortalDrop(Portal portal, bool isTopCommit);
extern Portal GetPortalByName(const char *name);
extern void PortalDefineQuery(Portal portal,
							  const char *prepStmtName,
							  const char *sourceText,
							  CommandTag commandTag,
							  List *stmts,
							  CachedPlan *cplan);
extern PlannedStmt *PortalGetPrimaryStmt(Portal portal);
extern void PortalCreateHoldStore(Portal portal);
extern void PortalHashTableDeleteAll(void);
extern bool ThereAreNoReadyPortals(void);
extern void HoldPinnedPortals(void);
extern void ForgetPortalSnapshots(void);

#endif							/* PORTAL_H */
